/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_renderCommon.cpp - common rendering passes
//


#include "r_local.h"


/*
 ==============================================================================

 Z-FILL RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_DrawDepth
 ==================
*/
static void RB_DrawDepth (){

	stage_t	*stage;
	bool	alphaTested = false;
	int		i;

	RB_Cull();

	if (backEnd.material->coverage == MC_PERFORATED){
		for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++){
			if (stage->lighting != SL_AMBIENT && stage->lighting != SL_DIFFUSE)
				continue;

			if (!backEnd.registers[stage->conditionRegister])
				continue;

			if (stage->shaderStage.program)
				continue;

			if (!(stage->drawState & DS_ALPHATEST))
				continue;

			if (!alphaTested){
				alphaTested = true;

				if (glConfig.multiSamples > 1 && r_alphaToCoverage->integerValue)
					qglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
			}

			if (stage->drawState & DS_POLYGONOFFSET){
				GL_Enable(GL_POLYGON_OFFSET_FILL);
				GL_PolygonOffset(r_offsetFactor->floatValue, r_offsetUnits->floatValue * stage->polygonOffset);
			}
			else
				GL_Disable(GL_POLYGON_OFFSET_FILL);

			GL_Enable(GL_ALPHA_TEST);
			GL_AlphaFunc(GL_GREATER, backEnd.registers[stage->alphaTestRegister]);

			RB_SetupTextureStage(&stage->textureStage);
			RB_SetupColorStage(&stage->colorStage);

			RB_DrawElements();

			RB_CleanupColorStage(&stage->colorStage);
			RB_CleanupTextureStage(&stage->textureStage);
		}

		if (alphaTested){
			qglColor3f(0.0f, 0.0f, 0.0f);

			GL_DisableTexture();

			GL_Disable(GL_ALPHA_TEST);

			if (glConfig.multiSamples > 1 && r_alphaToCoverage->integerValue)
				qglDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);

			return;
		}
	}

	RB_PolygonOffset();

	RB_DrawElements();
}

/*
 ==================
 RB_FillDepthBuffer
 ==================
*/
void RB_FillDepthBuffer (int numDrawSurfaces, drawSurface_t **drawSurfaces){

	drawSurface_t	*ds;
	uint			sort;
	bool			skip;
	int				i;

	if (!numDrawSurfaces)
		return;

	QGL_LogPrintf("---------- RB_FillDepthBuffer ----------\n");

	// Set depth filling mode
	backEnd.depthFilling = true;

	// Set the GL state
	GL_DisableTexture();

	GL_PolygonMode(GL_FILL);

	GL_Disable(GL_BLEND);

	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Disable(GL_STENCIL_TEST);

	if (backEnd.viewParms.primaryView)
		GL_ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	else
		GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

	GL_DepthMask(GL_TRUE);
	GL_StencilMask(0);

	qglColor3f(0.0f, 0.0f, 0.0f);

	// Clear the batch state
	backEnd.entity = NULL;
	backEnd.material = NULL;

	sort = 0;

	// Run through the surfaces
	for (i = 0; i < numDrawSurfaces; i++){
		ds = drawSurfaces[i];

		// Check if the state changed
		if (ds->sort != sort){
			sort = ds->sort;

			// Draw the last batch
			RB_RenderBatch();

			// Skip if condition evaluated to false
			if (!ds->registers[ds->material->conditionRegister]){
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (ds->entity != backEnd.entity)
				RB_EntityState(ds->entity);

			// Create a new batch
			RB_CreateBatch(ds->entity, ds->material, ds->registers, RB_DrawDepth);

			skip = false;
		}

		if (skip)
			continue;

		// Batch the surface geometry
		RB_BatchGeometry(ds->type, ds->geometry);
	}

	// Draw the last batch
	RB_RenderBatch();

	// Clear depth filling mode
	backEnd.depthFilling = false;

	QGL_LogPrintf("--------------------\n");
}


/*
 ==============================================================================

 AMBIENT RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_DrawMaterial
 ==================
*/
static void RB_DrawMaterial (){

	stage_t	*stage;
	int		i;

	QGL_LogPrintf("----- RB_DrawMaterial ( %s ) -----\n", backEnd.material->name);

	RB_Cull();

	for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++){
		if (stage->lighting != SL_AMBIENT)
			continue;

		if (!backEnd.registers[stage->conditionRegister])
			continue;

		// Special case for custom shaders
		if (stage->shaderStage.program){
			if (r_skipShaders->integerValue)
				continue;

			RB_DrawState(stage);

			RB_SetupShaderStage(&stage->shaderStage);

			RB_DrawElements();

			RB_CleanupShaderStage(&stage->shaderStage);

			continue;
		}

		// General case
		RB_DrawState(stage);

		RB_SetupTextureStage(&stage->textureStage);
		RB_SetupColorStage(&stage->colorStage);

		RB_DrawElements();

		RB_CleanupColorStage(&stage->colorStage);
		RB_CleanupTextureStage(&stage->textureStage);
	}

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_RenderMaterialPasses
 ==================
*/
void RB_RenderMaterialPasses (int numDrawSurfaces, drawSurface_t **drawSurfaces, ambientPass_t pass){

	drawSurface_t	*ds;
	uint			sort;
	bool			skip;
	int				i;

	if (!numDrawSurfaces)
		return;

	// Development tools
	if (r_skipAmbient->integerValue)
		return;

	if (r_skipTranslucent->integerValue){
		if (pass == AP_TRANSLUCENT)
			return;
	}

	if (r_skipPostProcess->integerValue){
		if (pass == AP_POST_PROCESS)
			return;
	}

	QGL_LogPrintf("---------- RB_RenderMaterialPasses ----------\n");

	// Set the GL state
	GL_PolygonMode(GL_FILL);

	GL_Enable(GL_DEPTH_TEST);

	GL_Disable(GL_STENCIL_TEST);

	if (pass == AP_POST_PROCESS)
		GL_DepthMask(GL_TRUE);
	else
		GL_DepthMask(GL_FALSE);

	GL_StencilMask(0);

	// Enable seamless cube maps if desired
	if (r_seamlessCubeMaps->integerValue)
		qglEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);

	// Clear the batch state
	backEnd.entity = NULL;
	backEnd.material = NULL;

	sort = 0;

	// Run through the surfaces
	for (i = 0; i < numDrawSurfaces; i++){
		ds = drawSurfaces[i];

		// Check if the state changed
		if (ds->sort != sort){
			sort = ds->sort;

			// Draw the last batch
			RB_RenderBatch();

			ds->material->numAmbientStages = 1;

			// Skip if it doesn't have ambient stages
			if (!ds->material->numAmbientStages){
				skip = true;
				continue;
			}

			// Skip if condition evaluated to false
			if (!ds->registers[ds->material->conditionRegister]){
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (ds->entity != backEnd.entity)
				RB_EntityState(ds->entity);

			// Capture the current color if needed
			if ((ds->material->flags & MF_NEEDCURRENTCOLOR) && !(ds->material->flags & MF_UPDATECURRENTCOLOR)){
				if (backEnd.currentColorCaptured != ds->material->sort){
					backEnd.currentColorCaptured = ds->material->sort;

					R_CopyFramebufferToTexture(rg.currentColorTexture, 0, backEnd.viewport.X(), backEnd.viewport.Y(), backEnd.viewport.Width(), backEnd.viewport.Height());
				}
			}

			// Capture the current depth if needed
			if ((ds->material->flags & MF_NEEDCURRENTDEPTH) && !(ds->material->flags & MF_UPDATECURRENTDEPTH)){
				if (backEnd.currentDepthCaptured != true){
					backEnd.currentDepthCaptured = true;

					R_CopyFramebufferToTexture(rg.currentDepthTexture, 0, backEnd.viewport.X(), backEnd.viewport.Y(), backEnd.viewport.Width(), backEnd.viewport.Height());
				}
			}

			// Create a new batch
			RB_CreateBatch(ds->entity, ds->material, ds->registers, RB_DrawMaterial);

			skip = false;
		}

		if (skip)
			continue;

		// Batch the surface geometry
		RB_BatchGeometry(ds->type, ds->geometry);
	}

	// Draw the last batch
	RB_RenderBatch();

	// Disable seamless cube maps if desired
	if (r_seamlessCubeMaps->integerValue)
		qglDisable(GL_TEXTURE_CUBE_MAP_SEAMLESS);

	QGL_LogPrintf("--------------------\n");
}


/*
 ==============================================================================

 SHADOW & INTERACTION RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_DrawShadow
 ==================
*/
static void RB_DrawShadow (){

	stage_t	*stage;
	bool	alphaTested = false;
	int		i;

	RB_Cull();

	if (backEnd.material->coverage == MC_PERFORATED){
		for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++){
			if (stage->lighting != SL_AMBIENT && stage->lighting != SL_DIFFUSE)
				continue;

			if (!backEnd.registers[stage->conditionRegister])
				continue;

			if (stage->shaderStage.program)
				continue;

			if (!(stage->drawState & DS_ALPHATEST))
				continue;

			if (!alphaTested)
				alphaTested = true;

			GL_Enable(GL_ALPHA_TEST);
			GL_AlphaFunc(GL_GREATER, backEnd.registers[stage->alphaTestRegister]);

			RB_SetupTextureStage(&stage->textureStage);
			RB_SetupColorStage(&stage->colorStage);

			RB_DrawElementsWithCounters(&rg.pc.shadowIndices, &rg.pc.shadowVertices);

			RB_CleanupColorStage(&stage->colorStage);
			RB_CleanupTextureStage(&stage->textureStage);
		}

		if (alphaTested){
			GL_DisableTexture();

			GL_Disable(GL_ALPHA_TEST);

			return;
		}
	}

	RB_DrawElementsWithCounters(&rg.pc.shadowIndices, &rg.pc.shadowVertices);
}

/*
 ==================
 RB_ShadowPass
 ==================
*/
static void RB_ShadowPass (int numDrawSurfaces, drawSurface_t **drawSurfaces, int bit){

	drawSurface_t	*ds;
	uint			sort;
	bool			skip;
	int				i;

	if (!numDrawSurfaces)
		return;

	QGL_LogPrintf("---------- RB_ShadowPass ----------\n");

	// Set depth filling mode
	backEnd.depthFilling = true;

	// Set the GL state
	GL_DisableTexture();

	GL_PolygonMode(GL_FILL);

	GL_Enable(GL_POLYGON_OFFSET_FILL);
	GL_PolygonOffset(r_shadowOffsetFactor->floatValue * backEnd.light->l.shadowSlopeScaleBias, r_shadowOffsetUnits->floatValue * backEnd.light->l.shadowBias);

	GL_Disable(GL_BLEND);

	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	GL_DepthMask(GL_TRUE);
	GL_StencilMask(0);

	// Clear the batch state
	backEnd.entity = NULL;
	backEnd.material = NULL;

	sort = 0;

	// Run through the surfaces
	for (i = 0; i < numDrawSurfaces; i++){
		ds = drawSurfaces[i];

		// Check visibility
		if (!(ds->bits & BIT(bit)))
			continue;

		// Check if the state changed
		if (ds->sort != sort){
			sort = ds->sort;

			// Draw the last batch
			RB_RenderBatch();

			// Development tool
			if (r_skipTranslucent->integerValue && ds->material->coverage == MC_TRANSLUCENT){
				skip = true;
				continue;
			}

			// Skip if condition evaluated to false
			if (!ds->registers[ds->material->conditionRegister]){
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (ds->entity != backEnd.entity)
				RB_EntityShadowState(ds->entity, backEnd.light);

			// Create a new batch
			RB_CreateBatch(ds->entity, ds->material, ds->registers, RB_DrawShadow);

			skip = false;
		}

		if (skip)
			continue;

		// Batch the surface geometry
		RB_BatchGeometry(ds->type, ds->geometry);
	}

	// Draw the last batch
	RB_RenderBatch();

	// Clear depth filling mode
	backEnd.depthFilling = false;

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_RenderShadowMap
 ==================
*/
static void RB_RenderShadowMap (int numDrawSurfaces, drawSurface_t **drawSurfaces, int shadowLOD, int shadowFilter, int shadowCubeBits, int numShadowSplits, const glqVec2 shadowSplitScales[MAX_SHADOW_SPLITS], const glqVec2 shadowSplitBiases[MAX_SHADOW_SPLITS]){

	static glqAngles	shadowCubeAngles[6] = {glqAngles(0.0f, 0.0f, 90.0f), glqAngles(0.0f, 180.0f, 90.0f), glqAngles(0.0f, 90.0f, 0.0f), glqAngles(0.0f, -90.0f, 0.0f), glqAngles(-90.0f, 90.0f, 0.0f), glqAngles(90.0f, 90.0f, 0.0f)};
	static float		shadowSamples[4] = {1.0f, 4.0f, 16.0f, 64.0f};
	framebuffer_t		*shadowBuffer;
	int					i;

	if (!numDrawSurfaces)
		return;

	// Development tool
	if (r_skipShadows->integerValue)
		return;

	QGL_LogPrintf("---------- RB_RenderShadowMap ----------\n");

	// Disable the clip plane if needed
	if (backEnd.viewParms.viewType == VIEW_MIRROR)
		qglDisable(GL_CLIP_PLANE0);

	// Render the shadow map
	if (backEnd.light->l.type == RL_PROJECTED){
		shadowBuffer = backEnd.shadowBuffers[shadowLOD];

		rg.pc.renderTextures++;
		rg.pc.renderTexturePixels += shadowBuffer->width * shadowBuffer->height;

		// Compute the projection matrix
		backEnd.shadowMap.projectionMatrix[0][0] = 2.0f * backEnd.light->l.zNear / (backEnd.light->l.xMax - backEnd.light->l.xMin);
		backEnd.shadowMap.projectionMatrix[0][1] = 0.0f;
		backEnd.shadowMap.projectionMatrix[0][2] = 0.0f;
		backEnd.shadowMap.projectionMatrix[0][3] = 0.0f;
		backEnd.shadowMap.projectionMatrix[1][0] = 0.0f;
		backEnd.shadowMap.projectionMatrix[1][1] = 2.0f * backEnd.light->l.zNear / (backEnd.light->l.yMax - backEnd.light->l.yMin);
		backEnd.shadowMap.projectionMatrix[1][2] = 0.0f;
		backEnd.shadowMap.projectionMatrix[1][3] = 0.0f;
		backEnd.shadowMap.projectionMatrix[2][0] = (backEnd.light->l.xMax + backEnd.light->l.xMin) / (backEnd.light->l.xMax - backEnd.light->l.xMin);
		backEnd.shadowMap.projectionMatrix[2][1] = (backEnd.light->l.yMax + backEnd.light->l.yMin) / (backEnd.light->l.yMax - backEnd.light->l.yMin);
		backEnd.shadowMap.projectionMatrix[2][2] = -(backEnd.light->l.zFar + backEnd.light->l.zNear) / (backEnd.light->l.zFar - backEnd.light->l.zNear);
		backEnd.shadowMap.projectionMatrix[2][3] = -1.0f;
		backEnd.shadowMap.projectionMatrix[3][0] = 0.0f;
		backEnd.shadowMap.projectionMatrix[3][1] = 0.0f;
		backEnd.shadowMap.projectionMatrix[3][2] = -2.0f * backEnd.light->l.zFar * backEnd.light->l.zNear / (backEnd.light->l.zFar - backEnd.light->l.zNear);
		backEnd.shadowMap.projectionMatrix[3][3] = 0.0f;

		// Compute the modelview matrix
		backEnd.shadowMap.modelviewMatrix[0][0] = -backEnd.light->data.axis[1][0];
		backEnd.shadowMap.modelviewMatrix[0][1] = backEnd.light->data.axis[2][0];
		backEnd.shadowMap.modelviewMatrix[0][2] = -backEnd.light->data.axis[0][0];
		backEnd.shadowMap.modelviewMatrix[0][3] = 0.0f;
		backEnd.shadowMap.modelviewMatrix[1][0] = -backEnd.light->data.axis[1][1];
		backEnd.shadowMap.modelviewMatrix[1][1] = backEnd.light->data.axis[2][1];
		backEnd.shadowMap.modelviewMatrix[1][2] = -backEnd.light->data.axis[0][1];
		backEnd.shadowMap.modelviewMatrix[1][3] = 0.0f;
		backEnd.shadowMap.modelviewMatrix[2][0] = -backEnd.light->data.axis[1][2];
		backEnd.shadowMap.modelviewMatrix[2][1] = backEnd.light->data.axis[2][2];
		backEnd.shadowMap.modelviewMatrix[2][2] = -backEnd.light->data.axis[0][2];
		backEnd.shadowMap.modelviewMatrix[2][3] = 0.0f;
		backEnd.shadowMap.modelviewMatrix[3][0] = backEnd.light->data.origin.Dot(backEnd.light->data.axis[1]);
		backEnd.shadowMap.modelviewMatrix[3][1] = -backEnd.light->data.origin.Dot(backEnd.light->data.axis[2]);
		backEnd.shadowMap.modelviewMatrix[3][2] = backEnd.light->data.origin.Dot(backEnd.light->data.axis[0]);
		backEnd.shadowMap.modelviewMatrix[3][3] = 1.0f;

		// Compute the modelview-projection matrix
		backEnd.shadowMap.modelviewProjectionMatrix = backEnd.shadowMap.projectionMatrix * backEnd.shadowMap.modelviewMatrix;

		// Bind and set up the framebuffer
		GL_BindFramebuffer(shadowBuffer);

		R_AttachDepthTextureToFramebuffer(shadowBuffer, shadowBuffer->depthTexture, 0);

		// Set up the viewport
		GL_Viewport(shadowBuffer->rect);

		// Set up the scissor
		GL_Scissor(shadowBuffer->rect);

		// Set the GL state
		GL_LoadMatrix(GL_PROJECTION, backEnd.shadowMap.projectionMatrix);
		GL_LoadMatrix(GL_MODELVIEW, backEnd.shadowMap.modelviewMatrix);

		// Clear the depth buffer
		GL_DepthMask(GL_TRUE);

		qglClearDepth(1.0f);
		qglClear(GL_DEPTH_BUFFER_BIT);

		// Draw the surfaces
		RB_ShadowPass(numDrawSurfaces, drawSurfaces, 0);
	}
	else if (backEnd.light->l.type == RL_POINT || backEnd.light->l.type == RL_CUBIC){
		shadowBuffer = backEnd.shadowCubeBuffers[shadowLOD];

		for (i = 0; i < 6; i++){
			if (!(shadowCubeBits & BIT(i)))
				continue;

			rg.pc.renderTextures++;
			rg.pc.renderTexturePixels += shadowBuffer->width * shadowBuffer->height;

			// Update the axis for this cube face
			backEnd.light->data.axis = shadowCubeAngles[i].ToMat3();

			// Compute the projection matrix
			backEnd.shadowMap.projectionMatrix[0][0] = (i & 1) ? -1.0f : 1.0f;
			backEnd.shadowMap.projectionMatrix[0][1] = 0.0f;
			backEnd.shadowMap.projectionMatrix[0][2] = 0.0f;
			backEnd.shadowMap.projectionMatrix[0][3] = 0.0f;
			backEnd.shadowMap.projectionMatrix[1][0] = 0.0f;
			backEnd.shadowMap.projectionMatrix[1][1] = (i & 1) ? -1.0f : 1.0f;
			backEnd.shadowMap.projectionMatrix[1][2] = 0.0f;
			backEnd.shadowMap.projectionMatrix[1][3] = 0.0f;
			backEnd.shadowMap.projectionMatrix[2][0] = 0.0f;
			backEnd.shadowMap.projectionMatrix[2][1] = 0.0f;
			backEnd.shadowMap.projectionMatrix[2][2] = -(backEnd.light->data.lightRange + 1.0f) / (backEnd.light->data.lightRange - 1.0f);
			backEnd.shadowMap.projectionMatrix[2][3] = -1.0f;
			backEnd.shadowMap.projectionMatrix[3][0] = 0.0f;
			backEnd.shadowMap.projectionMatrix[3][1] = 0.0f;
			backEnd.shadowMap.projectionMatrix[3][2] = -2.0f * backEnd.light->data.lightRange / (backEnd.light->data.lightRange - 1.0f);
			backEnd.shadowMap.projectionMatrix[3][3] = 0.0f;

			// Compute the modelview matrix
			backEnd.shadowMap.modelviewMatrix[0][0] = -backEnd.light->data.axis[1][0];
			backEnd.shadowMap.modelviewMatrix[0][1] = backEnd.light->data.axis[2][0];
			backEnd.shadowMap.modelviewMatrix[0][2] = -backEnd.light->data.axis[0][0];
			backEnd.shadowMap.modelviewMatrix[0][3] = 0.0f;
			backEnd.shadowMap.modelviewMatrix[1][0] = -backEnd.light->data.axis[1][1];
			backEnd.shadowMap.modelviewMatrix[1][1] = backEnd.light->data.axis[2][1];
			backEnd.shadowMap.modelviewMatrix[1][2] = -backEnd.light->data.axis[0][1];
			backEnd.shadowMap.modelviewMatrix[1][3] = 0.0f;
			backEnd.shadowMap.modelviewMatrix[2][0] = -backEnd.light->data.axis[1][2];
			backEnd.shadowMap.modelviewMatrix[2][1] = backEnd.light->data.axis[2][2];
			backEnd.shadowMap.modelviewMatrix[2][2] = -backEnd.light->data.axis[0][2];
			backEnd.shadowMap.modelviewMatrix[2][3] = 0.0f;
			backEnd.shadowMap.modelviewMatrix[3][0] = backEnd.light->data.origin.Dot(backEnd.light->data.axis[1]);
			backEnd.shadowMap.modelviewMatrix[3][1] = -backEnd.light->data.origin.Dot(backEnd.light->data.axis[2]);
			backEnd.shadowMap.modelviewMatrix[3][2] = backEnd.light->data.origin.Dot(backEnd.light->data.axis[0]);
			backEnd.shadowMap.modelviewMatrix[3][3] = 1.0f;

			// Compute the modelview-projection matrix
			backEnd.shadowMap.modelviewProjectionMatrix = backEnd.shadowMap.projectionMatrix * backEnd.shadowMap.modelviewMatrix;

			// Bind and set up the framebuffer
			GL_BindFramebuffer(shadowBuffer);

			R_AttachDepthTextureToFramebuffer(shadowBuffer, shadowBuffer->depthTexture, i);

			// Set up the viewport
			GL_Viewport(shadowBuffer->rect);

			// Set up the scissor
			GL_Scissor(shadowBuffer->rect);

			// Set the GL state
			GL_LoadMatrix(GL_PROJECTION, backEnd.shadowMap.projectionMatrix);
			GL_LoadMatrix(GL_MODELVIEW, backEnd.shadowMap.modelviewMatrix);

			// Clear the depth buffer
			GL_DepthMask(GL_TRUE);

			qglClearDepth(1.0f);
			qglClear(GL_DEPTH_BUFFER_BIT);

			// Draw the surfaces
			RB_ShadowPass(numDrawSurfaces, drawSurfaces, i);
		}
	}
	else {
		shadowBuffer = backEnd.shadowArrayBuffer;

		backEnd.shadowMap.numSplits = numShadowSplits;

		for (i = 0; i < MAX_SHADOW_SPLITS; i++)
			backEnd.shadowMap.splitScales[i].Clear();

		for (i = 0; i < numShadowSplits; i++){
			rg.pc.renderTextures++;
			rg.pc.renderTexturePixels += shadowBuffer->width * shadowBuffer->height;

			// Compute the projection matrix
			backEnd.shadowMap.projectionMatrix[0][0] = shadowSplitScales[i][0];
			backEnd.shadowMap.projectionMatrix[0][1] = 0.0f;
			backEnd.shadowMap.projectionMatrix[0][2] = 0.0f;
			backEnd.shadowMap.projectionMatrix[0][3] = 0.0f;
			backEnd.shadowMap.projectionMatrix[1][0] = 0.0f;
			backEnd.shadowMap.projectionMatrix[1][1] = shadowSplitScales[i][1];
			backEnd.shadowMap.projectionMatrix[1][2] = 0.0f;
			backEnd.shadowMap.projectionMatrix[1][3] = 0.0f;
			backEnd.shadowMap.projectionMatrix[2][0] = 0.0f;
			backEnd.shadowMap.projectionMatrix[2][1] = 0.0f;
			backEnd.shadowMap.projectionMatrix[2][2] = -2.0f / backEnd.light->data.lightRange;
			backEnd.shadowMap.projectionMatrix[2][3] = 0.0f;
			backEnd.shadowMap.projectionMatrix[3][0] = -shadowSplitBiases[i][0];
			backEnd.shadowMap.projectionMatrix[3][1] = -shadowSplitBiases[i][1];
			backEnd.shadowMap.projectionMatrix[3][2] = -1.0f;
			backEnd.shadowMap.projectionMatrix[3][3] = 1.0f;

			// Compute the modelview matrix
			backEnd.shadowMap.modelviewMatrix[0][0] = -backEnd.light->data.axis[1][0];
			backEnd.shadowMap.modelviewMatrix[0][1] = backEnd.light->data.axis[2][0];
			backEnd.shadowMap.modelviewMatrix[0][2] = -backEnd.light->data.axis[0][0];
			backEnd.shadowMap.modelviewMatrix[0][3] = 0.0f;
			backEnd.shadowMap.modelviewMatrix[1][0] = -backEnd.light->data.axis[1][1];
			backEnd.shadowMap.modelviewMatrix[1][1] = backEnd.light->data.axis[2][1];
			backEnd.shadowMap.modelviewMatrix[1][2] = -backEnd.light->data.axis[0][1];
			backEnd.shadowMap.modelviewMatrix[1][3] = 0.0f;
			backEnd.shadowMap.modelviewMatrix[2][0] = -backEnd.light->data.axis[1][2];
			backEnd.shadowMap.modelviewMatrix[2][1] = backEnd.light->data.axis[2][2];
			backEnd.shadowMap.modelviewMatrix[2][2] = -backEnd.light->data.axis[0][2];
			backEnd.shadowMap.modelviewMatrix[2][3] = 0.0f;
			backEnd.shadowMap.modelviewMatrix[3][0] = backEnd.light->data.origin.Dot(backEnd.light->data.axis[1]);
			backEnd.shadowMap.modelviewMatrix[3][1] = -backEnd.light->data.origin.Dot(backEnd.light->data.axis[2]);
			backEnd.shadowMap.modelviewMatrix[3][2] = backEnd.light->data.origin.Dot(backEnd.light->data.axis[0]);
			backEnd.shadowMap.modelviewMatrix[3][3] = 1.0f;

			// Compute the modelview-projection matrix
			backEnd.shadowMap.modelviewProjectionMatrix = backEnd.shadowMap.projectionMatrix * backEnd.shadowMap.modelviewMatrix;

			// Store the matrices in the arrays
			backEnd.shadowMap.projectionMatrixArray[i] = backEnd.shadowMap.projectionMatrix;
			backEnd.shadowMap.modelviewMatrixArray[i] = backEnd.shadowMap.modelviewMatrix;
			backEnd.shadowMap.modelviewProjectionMatrixArray[i] = backEnd.shadowMap.modelviewProjectionMatrix;

			// Set up the split scales
			backEnd.shadowMap.splitScales[i] = shadowSplitScales[i];

			// Bind and set up the framebuffer
			GL_BindFramebuffer(shadowBuffer);

			R_AttachDepthTextureToFramebuffer(shadowBuffer, shadowBuffer->depthTexture, i);

			// Set up the viewport
			GL_Viewport(shadowBuffer->rect);

			// Set up the scissor
			GL_Scissor(shadowBuffer->rect);

			// Set the GL state
			GL_LoadMatrix(GL_PROJECTION, backEnd.shadowMap.projectionMatrix);
			GL_LoadMatrix(GL_MODELVIEW, backEnd.shadowMap.modelviewMatrix);

			// Clear the depth buffer
			GL_DepthMask(GL_TRUE);

			qglClearDepth(1.0f);
			qglClear(GL_DEPTH_BUFFER_BIT);

			// Draw the surfaces
			RB_ShadowPass(numDrawSurfaces, drawSurfaces, i);
		}
	}

	// Unbind the framebuffer
	GL_BindFramebuffer(NULL);

	// Restore the viewport
	GL_Viewport(backEnd.viewport);

	// Restore the scissor
	GL_Scissor(backEnd.scissor);

	// Restore the GL state
	GL_LoadMatrix(GL_PROJECTION, backEnd.viewParms.projectionMatrix);

	// Enable the clip plane if needed
	if (backEnd.viewParms.viewType == VIEW_MIRROR)
		qglEnable(GL_CLIP_PLANE0);

	// Shadow map now available for this light
	backEnd.shadowMap.available = true;

	// Set up the shadow and jitter parameters
	if (r_skipSoftShadows->integerValue){
		backEnd.shadowMap.shadowParms.Set(shadowSamples[shadowFilter], 1.0f / shadowSamples[shadowFilter], 0.0f, 0.0f);
		backEnd.shadowMap.jitterParms.Set(1.0f / 32.0f, 2.0f / shadowSamples[shadowFilter], 0.0f, 0.0f);
	}
	else {
		backEnd.shadowMap.shadowParms.Set(shadowSamples[shadowFilter], 1.0f / shadowSamples[shadowFilter], backEnd.light->data.shadowBiasScale[0] * backEnd.light->l.shadowSoftness, backEnd.light->data.shadowBiasScale[1] * backEnd.light->l.shadowSoftness);
		backEnd.shadowMap.jitterParms.Set(1.0f / 32.0f, 2.0f / shadowSamples[shadowFilter], backEnd.light->data.shadowJitterScale[0] * backEnd.light->l.shadowSoftness, backEnd.light->data.shadowJitterScale[1] * backEnd.light->l.shadowSoftness);
	}

	// Set up the shadow and jitter textures
	backEnd.shadowMap.shadowTexture = shadowBuffer->depthTexture;
	backEnd.shadowMap.jitterTexture = rg.jitterTextures[shadowFilter];

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_DrawInteractions
 ==================
*/
static void RB_DrawInteractions (){

	drawInteraction_t	di;
	stage_t				*stage, *lightStage;
	cinData_t			data;
	int					i, j;

	QGL_LogPrintf("----- RB_DrawInteractions ( %s on %s ) -----\n", backEnd.lightMaterial->name, backEnd.material->name);

	RB_Cull();
	RB_PolygonOffset();

	qglVertexAttribPointer(GL_ATTRIB_NORMAL, 3, GL_FLOAT, false, sizeof(glVertex_t), GL_VERTEX_NORMAL(backEnd.vertexPointer));
	qglVertexAttribPointer(GL_ATTRIB_TANGENT1, 3, GL_FLOAT, false, sizeof(glVertex_t), GL_VERTEX_TANGENT1(backEnd.vertexPointer));
	qglVertexAttribPointer(GL_ATTRIB_TANGENT2, 3, GL_FLOAT, false, sizeof(glVertex_t), GL_VERTEX_TANGENT2(backEnd.vertexPointer));
	qglVertexAttribPointer(GL_ATTRIB_TEXCOORD, 2, GL_FLOAT, false, sizeof(glVertex_t), GL_VERTEX_TEXCOORD(backEnd.vertexPointer));
	qglVertexAttribPointer(GL_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof(glVertex_t), GL_VERTEX_COLOR(backEnd.vertexPointer));

	// Run through the light stages
	for (i = 0, lightStage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, lightStage++){
		if (!backEnd.lightRegisters[lightStage->conditionRegister])
			continue;

		// Compute the light matrix
		RB_ComputeLightMatrix(backEnd.light, backEnd.entity, &lightStage->textureStage);

		// Compute the shadow matrix
		RB_ComputeShadowMatrix(backEnd.light, backEnd.entity);

		// Set up the interaction
		di.bumpTexture = NULL;
		di.diffuseTexture = NULL;
		di.specularTexture = NULL;
		di.lightProjectionTexture = lightStage->textureStage.texture;
		di.lightFalloffTexture = backEnd.lightMaterial->lightFalloffImage;
		di.lightCubeTexture = backEnd.lightMaterial->lightCubeImage;

		di.colorScaleAndBias.Set(0.0f, 1.0f);

		di.lightColor[0] = backEnd.lightRegisters[lightStage->colorStage.registers[0]] * r_lightScale->floatValue;
		di.lightColor[1] = backEnd.lightRegisters[lightStage->colorStage.registers[1]] * r_lightScale->floatValue;
		di.lightColor[2] = backEnd.lightRegisters[lightStage->colorStage.registers[2]] * r_lightScale->floatValue;

		// If we have a cinematic
		if (lightStage->textureStage.cinematicHandle){
			if (r_skipVideos->integerValue)
				di.lightProjectionTexture = rg.blackTexture;
			else {
				// Decode a video frame
				data = CIN_UpdateCinematic(lightStage->textureStage.cinematicHandle, backEnd.time);

				// Update the texture if needed
				if (!data.image)
					di.lightProjectionTexture = rg.blackTexture;
				else if (data.dirty)
					R_UploadTextureImage(di.lightProjectionTexture, TMU_LIGHTPROJECTION, data.image, data.width, data.height);
			}
		}

		// Run through the surface stages
		for (j = 0, stage = backEnd.material->stages; j < backEnd.material->numStages; j++, stage++){
			if (stage->lighting == SL_AMBIENT)
				continue;

			if (!backEnd.registers[stage->conditionRegister])
				continue;

			// Combine multiple stages and draw interactions
			switch (stage->lighting){
			case SL_BUMP:
				if (di.bumpTexture){
					RB_DrawSingleInteraction(&di);

					di.diffuseTexture = NULL;
					di.specularTexture = NULL;
				}

				di.bumpTexture = stage->textureStage.texture;

				RB_ComputeTextureMatrix(&stage->textureStage, backEnd.registers, di.bumpMatrix);

				break;
			case SL_DIFFUSE:
				if (di.diffuseTexture)
					RB_DrawSingleInteraction(&di);

				di.diffuseTexture = stage->textureStage.texture;

				di.colorScaleAndBias.Set(stage->colorStage.scale, stage->colorStage.bias);

				di.diffuseColor[0] = backEnd.registers[stage->colorStage.registers[0]];
				di.diffuseColor[1] = backEnd.registers[stage->colorStage.registers[1]];
				di.diffuseColor[2] = backEnd.registers[stage->colorStage.registers[2]];

				RB_ComputeTextureMatrix(&stage->textureStage, backEnd.registers, di.diffuseMatrix);

				break;
			case SL_SPECULAR:
				if (di.specularTexture)
					RB_DrawSingleInteraction(&di);

				di.specularTexture = stage->textureStage.texture;

				di.specularColor[0] = backEnd.registers[stage->colorStage.registers[0]];
				di.specularColor[1] = backEnd.registers[stage->colorStage.registers[1]];
				di.specularColor[2] = backEnd.registers[stage->colorStage.registers[2]];

				di.specularParms[0] = stage->parms[0];
				di.specularParms[1] = stage->parms[1];

				RB_ComputeTextureMatrix(&stage->textureStage, backEnd.registers, di.specularMatrix);

				break;
			}
		}

		// Draw the last interaction
		if (!di.bumpTexture && !di.diffuseTexture && !di.specularTexture)
			continue;

		RB_DrawSingleInteraction(&di);
	}

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_InteractionPass
 ==================
*/
static void RB_InteractionPass (int numDrawSurfaces, drawSurface_t **drawSurfaces){

	drawSurface_t	*ds;
	uint			sort;
	bool			skip;
	int				i;

	if (!numDrawSurfaces)
		return;

	QGL_LogPrintf("---------- RB_InteractionPass ----------\n");

	// Set the GL state
	GL_PolygonMode(GL_FILL);

	GL_Enable(GL_BLEND);
	GL_BlendFunc(GL_ONE, GL_ONE);
	GL_BlendEquation(GL_FUNC_ADD);

	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);

	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	// Enable the arrays
	qglEnableVertexAttribArray(GL_ATTRIB_NORMAL);
	qglEnableVertexAttribArray(GL_ATTRIB_TANGENT1);
	qglEnableVertexAttribArray(GL_ATTRIB_TANGENT2);
	qglEnableVertexAttribArray(GL_ATTRIB_TEXCOORD);
	qglEnableVertexAttribArray(GL_ATTRIB_COLOR);

	// Clear the batch state
	backEnd.entity = NULL;
	backEnd.material = NULL;

	sort = 0;

	// Run through the surfaces
	for (i = 0; i < numDrawSurfaces; i++){
		ds = drawSurfaces[i];

		// Check if the state changed
		if (ds->sort != sort){
			sort = ds->sort;

			// Draw the last batch
			RB_RenderBatch();

			// Development tool
			if (r_skipTranslucent->integerValue && ds->material->coverage == MC_TRANSLUCENT){
				skip = true;
				continue;
			}

			// Skip if condition evaluated to false
			if (!ds->registers[ds->material->conditionRegister]){
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (ds->entity != backEnd.entity){
				RB_EntityState(ds->entity);

				// Transform the light for this entity
				RB_TransformLightForEntity(backEnd.light, ds->entity);
			}

			// Set the GL state
			if (ds->material->coverage != MC_TRANSLUCENT)
				GL_DepthFunc(GL_EQUAL);
			else
				GL_DepthFunc(GL_LEQUAL);

			// Create a new batch
			RB_CreateBatch(ds->entity, ds->material, ds->registers, RB_DrawInteractions);

			skip = false;
		}

		if (skip)
			continue;

		// Batch the surface geometry
		RB_BatchGeometry(ds->type, ds->geometry);
	}

	// Draw the last batch
	RB_RenderBatch();

	// Restore the GL state
	GL_SelectTexture(0);

	// Unbind the program
	GL_BindProgram(NULL);

	// Disable the arrays
	qglDisableVertexAttribArray(GL_ATTRIB_COLOR);
	qglDisableVertexAttribArray(GL_ATTRIB_TEXCOORD);
	qglDisableVertexAttribArray(GL_ATTRIB_TANGENT2);
	qglDisableVertexAttribArray(GL_ATTRIB_TANGENT1);
	qglDisableVertexAttribArray(GL_ATTRIB_NORMAL);

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_RenderLights
 ==================
*/
void RB_RenderLights (int numDrawLights, drawLight_t **drawLights){

	drawLight_t	*dl;
	int			i;

	if (!numDrawLights)
		return;

	// Development tool
	if (r_skipInteractions->integerValue)
		return;

	QGL_LogPrintf("---------- RB_RenderLights ----------\n");

	// Run through the lights
	for (i = 0; i < numDrawLights; i++){
		dl = drawLights[i];

		if (!dl->numInteractionSurfaces){
			if (!dl->lightShaftsVisible)
				continue;
		}

		// Development tool
		if (r_skipAmbientLights->integerValue){
			if (dl->material->lightType == LT_AMBIENT)
				continue;
		}

		// Skip if condition evaluated to false
		if (!dl->registers[dl->material->conditionRegister])
			continue;

		// Set the light
		backEnd.light = dl->light;
		backEnd.lightMaterial = dl->material;
		backEnd.lightRegisters = dl->registers;

		// Shadow map not yet available for this light
		backEnd.shadowMap.available = false;

		// Reset the shadow and jitter textures
		if (dl->light->l.type == RL_PROJECTED)
			backEnd.shadowMap.shadowTexture = rg.shadowTextures[NUM_SHADOW_BUFFERS - 1];
		else if (dl->light->l.type == RL_POINT || dl->light->l.type == RL_CUBIC)
			backEnd.shadowMap.shadowTexture = rg.shadowCubeTextures[NUM_SHADOW_BUFFERS - 1];
		else
			backEnd.shadowMap.shadowTexture = rg.shadowArrayTexture;

		backEnd.shadowMap.jitterTexture = rg.jitterTextures[JITTER_1];

		// FIXME: RB_RenderShadowMap seems to randomly crash when editing lights, could be because it isn't ready (loaded)

		// Render the shadow map if needed
		RB_RenderShadowMap(dl->numShadowSurfaces, dl->shadowSurfaces, dl->shadowLOD, dl->shadowFilter, dl->shadowCubeBits, dl->numShadowSplits, dl->shadowSplitScales, dl->shadowSplitBiases);

		// Set up the scissor
		GL_Scissor(dl->scissor);

		// Draw the surfaces
		RB_InteractionPass(dl->numInteractionSurfaces, dl->interactionSurfaces);

		// Accumulate light shafts in an offscreen buffer if visible
		if (!dl->lightShaftsVisible)
			continue;

		RB_AccumLightShafts(dl->scissor);
	}

	// Restore the scissor
	GL_Scissor(backEnd.scissor);

	QGL_LogPrintf("--------------------\n");
}


/*
 ==============================================================================

 BLEND LIGHT INTERACTION RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_DrawBlendLight
 ==================
*/
static void RB_DrawBlendLight (){

	stage_t	*stage;
	glqVec4	color;
	int		i;

	RB_Cull();
	RB_PolygonOffset();

	// Run through the light stages
	for (i = 0, stage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, stage++){
		if (!backEnd.lightRegisters[stage->conditionRegister])
			continue;

		// Set the GL state
		if (stage->drawState & DS_BLEND){
			GL_Enable(GL_BLEND);
			GL_BlendFunc(stage->blendSrc, stage->blendDst);
			GL_BlendEquation(stage->blendMode);
		}
		else
			GL_Disable(GL_BLEND);

		// Compute the light matrix
		RB_ComputeLightMatrix(backEnd.light, backEnd.entity, &stage->textureStage);

		// Set the light color
		color[0] = backEnd.lightRegisters[stage->colorStage.registers[0]];
		color[1] = backEnd.lightRegisters[stage->colorStage.registers[1]];
		color[2] = backEnd.lightRegisters[stage->colorStage.registers[2]];
		color[3] = backEnd.lightRegisters[stage->colorStage.registers[3]];

		// Set up the program uniforms
		R_UniformMatrix4(backEnd.blendLightParms.lightMatrix, GL_TRUE, backEnd.localParms.lightMatrix);
		R_UniformVector4(backEnd.blendLightParms.lightColor, color);

		// Bind the textures
		GL_BindMultitexture(stage->textureStage.texture, TMU_LIGHTPROJECTION);
		GL_BindMultitexture(backEnd.lightMaterial->lightFalloffImage, TMU_LIGHTFALLOFF);

		// Draw it
		RB_DrawElementsWithCounters(&rg.pc.interactionIndices, &rg.pc.interactionVertices);
	}
}

/*
 ==================
 RB_BlendLightPass
 ==================
*/
static void RB_BlendLightPass (int numDrawSurfaces, drawSurface_t **drawSurfaces){

	drawSurface_t	*ds;
	uint			sort;
	bool			skip;
	int				i;

	if (!numDrawSurfaces)
		return;

	QGL_LogPrintf("---------- RB_BlendLightPass ----------\n");

	// Set the GL state
	GL_PolygonMode(GL_FILL);

	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_EQUAL);

	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	// Bind the program
	GL_BindProgram(rg.blendLightProgram);

	// Clear the batch state
	backEnd.entity = NULL;
	backEnd.material = NULL;

	sort = 0;

	// Run through the surfaces
	for (i = 0; i < numDrawSurfaces; i++){
		ds = drawSurfaces[i];

		// Check if the state changed
		if (ds->sort != sort){
			sort = ds->sort;

			// Draw the last batch
			RB_RenderBatch();

			// Skip if condition evaluated to false
			if (!ds->registers[ds->material->conditionRegister]){
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (ds->entity != backEnd.entity){
				RB_EntityState(ds->entity);

				// Transform the light for this entity
				RB_TransformLightForEntity(backEnd.light, ds->entity);
			}

			// Create a new batch
			RB_CreateBatch(ds->entity, ds->material, ds->registers, RB_DrawBlendLight);

			skip = false;
		}

		if (skip)
			continue;

		// Batch the surface geometry
		RB_BatchGeometry(ds->type, ds->geometry);
	}

	// Draw the last batch
	RB_RenderBatch();

	// Restore the GL state
	GL_SelectTexture(0);

	// Unbind the program
	GL_BindProgram(NULL);

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_RenderBlendLights
 ==================
*/
void RB_RenderBlendLights (int numDrawLights, drawLight_t **drawLights){

	drawLight_t	*dl;
	int			i;

	if (!numDrawLights)
		return;

	// Development tool
	if (r_skipBlendLights->integerValue)
		return;

	QGL_LogPrintf("---------- RB_RenderBlendLights ----------\n");

	// Run through the lights
	for (i = 0; i < numDrawLights; i++){
		dl = drawLights[i];

		if (!dl->numInteractionSurfaces)
			continue;

		// Skip if condition evaluated to false
		if (!dl->registers[dl->material->conditionRegister])
			continue;

		// Set the light
		backEnd.light = dl->light;
		backEnd.lightMaterial = dl->material;
		backEnd.lightRegisters = dl->registers;

		// Set up the scissor
		GL_Scissor(dl->scissor);

		// Draw the surfaces
		RB_BlendLightPass(dl->numInteractionSurfaces, dl->interactionSurfaces);
	}

	// Restore the scissor
	GL_Scissor(backEnd.scissor);

	QGL_LogPrintf("--------------------\n");
}


/*
 ==============================================================================

 FOG LIGHT INTERACTION RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_DrawFogLight
 ==================
*/
static void RB_DrawFogLight (){

	stage_t	*stage;
	glqVec4	color;
	int		i;

	RB_Cull();
	RB_PolygonOffset();

	// Run through the light stages
	for (i = 0, stage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, stage++){
		if (!backEnd.lightRegisters[stage->conditionRegister])
			continue;

		// Compute the light matrix
		RB_ComputeLightMatrix(backEnd.light, backEnd.entity, &stage->textureStage);

		// Compute the light color
		color[0] = backEnd.lightRegisters[stage->colorStage.registers[0]];
		color[1] = backEnd.lightRegisters[stage->colorStage.registers[1]];
		color[2] = backEnd.lightRegisters[stage->colorStage.registers[2]];
		color[3] = backEnd.lightRegisters[stage->colorStage.registers[3]];

		// Set up the program uniforms
		R_UniformMatrix4(backEnd.fogLightParms.lightMatrix, GL_TRUE, backEnd.localParms.lightMatrix);
		R_UniformVector4(backEnd.fogLightParms.lightColor, color);

		// Bind the textures
		GL_BindMultitexture(stage->textureStage.texture, TMU_LIGHTPROJECTION);
		GL_BindMultitexture(backEnd.lightMaterial->lightFalloffImage, TMU_LIGHTFALLOFF);

		// Draw it
		RB_DrawElementsWithCounters(&rg.pc.interactionIndices, &rg.pc.interactionVertices);
	}
}

/*
 ==================
 RB_DrawFogLightPlane
 ==================
*/
static void RB_DrawFogLightPlane (){

	static glIndex_t	indices[6] = {2, 3, 1, 2, 1, 0};
	stage_t				*stage;
	glqVec4				color;
	int					i;

	// Unbind the index buffer
	GL_BindIndexBuffer(NULL);

	// Unbind the vertex buffer
	GL_BindVertexBuffer(NULL);

	// Set the GL state
	GL_PolygonMode(GL_FILL);

	GL_Enable(GL_CULL_FACE);
	GL_CullFace(GL_BACK);

	GL_Disable(GL_POLYGON_OFFSET_FILL);

	GL_Enable(GL_BLEND);
	GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GL_BlendEquation(GL_FUNC_ADD);

	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	// Set the entity state
	RB_EntityState(backEnd.scene->worldEntity);

	// Bind the program
	GL_BindProgram(rg.fogLightProgram);

	// Set up the vertex array
	qglVertexPointer(3, GL_FLOAT, 0, backEnd.light->data.corners->ToFloatPtr());

	// Run through the light stages
	for (i = 0, stage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, stage++){
		if (!backEnd.lightRegisters[stage->conditionRegister])
			continue;

		// Compute the light matrix
		RB_ComputeLightMatrix(backEnd.light, backEnd.scene->worldEntity, &stage->textureStage);

		// Compute the light color
		color[0] = backEnd.lightRegisters[stage->colorStage.registers[0]];
		color[1] = backEnd.lightRegisters[stage->colorStage.registers[1]];
		color[2] = backEnd.lightRegisters[stage->colorStage.registers[2]];
		color[3] = backEnd.lightRegisters[stage->colorStage.registers[3]];

		// Set up the program uniforms
		R_UniformMatrix4(backEnd.fogLightParms.lightMatrix, GL_TRUE, backEnd.localParms.lightMatrix);
		R_UniformVector4(backEnd.fogLightParms.lightColor, color);

		// Bind the textures
		GL_BindMultitexture(stage->textureStage.texture, TMU_LIGHTPROJECTION);
		GL_BindMultitexture(backEnd.lightMaterial->lightFalloffImage, TMU_LIGHTFALLOFF);

		// Draw it
		RB_DrawElementsStaticIndices(8, 6, indices);

		// Check for errors
		if (!r_ignoreGLErrors->integerValue)
			GL_CheckForErrors();
	}

	// Restore the GL state
	GL_SelectTexture(0);

	// Unbind the program
	GL_BindProgram(NULL);
}

/*
 ==================
 RB_DrawFogLightVolume
 ==================
*/
static void RB_DrawFogLightVolume (){

	static glIndex_t	indices[36] = {3, 2, 6, 3, 6, 7, 0, 1, 5, 0, 5, 4, 2, 3, 1, 2, 1, 0, 4, 5, 7, 4, 7, 6, 1, 3, 7, 1, 7, 5, 2, 0, 4, 2, 4, 6};

	// Unbind the index buffer
	GL_BindIndexBuffer(NULL);

	// Unbind the vertex buffer
	GL_BindVertexBuffer(NULL);

	// Set the GL state
	GL_LoadMatrix(GL_MODELVIEW, backEnd.viewParms.modelviewMatrix);

	GL_DisableTexture();

	GL_PolygonMode(GL_FILL);

	GL_Disable(GL_CULL_FACE);

	GL_Disable(GL_POLYGON_OFFSET_FILL);

	GL_Disable(GL_BLEND);

	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_ALWAYS, 128, 255);
	GL_StencilOpSeparate(GL_KEEP, GL_KEEP, GL_INCR_WRAP, GL_DECR_WRAP, GL_KEEP, GL_KEEP);

	GL_DepthRange(0.0f, 1.0f);

	GL_ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(255);

	// Enable depth clamp
	qglEnable(GL_DEPTH_CLAMP);

	// Clear the stencil buffer
	qglClearStencil(128);
	qglClear(GL_STENCIL_BUFFER_BIT);

	// Set up the vertex array
	qglVertexPointer(3, GL_FLOAT, 0, backEnd.light->data.corners->ToFloatPtr());

	// Draw it
	RB_DrawElementsStaticIndices(8, 36, indices);

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();

	// Disable depth clamp
	qglDisable(GL_DEPTH_CLAMP);
}

/*
 ==================
 RB_FogLightPass
 ==================
*/
static void RB_FogLightPass (int numDrawSurfaces, drawSurface_t **drawSurfaces){

	drawSurface_t	*ds;
	uint			sort;
	bool			skip;
	int				i;

	if (!numDrawSurfaces)
		return;

	QGL_LogPrintf("---------- RB_FogLightPass ----------\n");

	// Set the GL state
	GL_PolygonMode(GL_FILL);

	GL_Enable(GL_BLEND);
	GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GL_BlendEquation(GL_FUNC_ADD);

	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_EQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_LESS, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	// Bind the program
	GL_BindProgram(rg.fogLightProgram);

	// Clear the batch state
	backEnd.entity = NULL;
	backEnd.material = NULL;

	sort = 0;

	// Run through the surfaces
	for (i = 0; i < numDrawSurfaces; i++){
		ds = drawSurfaces[i];

		// Check if the state changed
		if (ds->sort != sort){
			sort = ds->sort;

			// Draw the last batch
			RB_RenderBatch();

			// Skip if condition evaluated to false
			if (!ds->registers[ds->material->conditionRegister]){
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (ds->entity != backEnd.entity){
				RB_EntityState(ds->entity);

				// Transform the light for this entity
				RB_TransformLightForEntity(backEnd.light, ds->entity);
			}

			// Create a new batch
			RB_CreateBatch(ds->entity, ds->material, ds->registers, RB_DrawFogLight);

			skip = false;
		}

		if (skip)
			continue;

		// Batch the surface geometry
		RB_BatchGeometry(ds->type, ds->geometry);
	}

	// Draw the last batch
	RB_RenderBatch();

	// Restore the GL state
	GL_SelectTexture(0);

	// Unbind the program
	GL_BindProgram(NULL);

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_RenderFogLights
 ==================
*/
void RB_RenderFogLights (int numDrawLights, drawLight_t **drawLights){

	drawLight_t	*dl;
	int			i;

	if (!numDrawLights)
		return;

	// Development tool
	if (r_skipFogLights->integerValue)
		return;

	QGL_LogPrintf("---------- RB_RenderFogLights ----------\n");

	// Run through the lights
	for (i = 0; i < numDrawLights; i++){
		dl = drawLights[i];

		if (!dl->numInteractionSurfaces){
			if (!dl->fogPlaneVisible)
				continue;
		}

		// Skip if condition evaluated to false
		if (!dl->registers[dl->material->conditionRegister])
			continue;

		// Set the light
		backEnd.light = dl->light;
		backEnd.lightMaterial = dl->material;
		backEnd.lightRegisters = dl->registers;

		// Set up the scissor
		GL_Scissor(dl->scissor);

		// Draw the fog volume to the stencil buffer
		RB_DrawFogLightVolume();

		// Draw the surfaces
		RB_FogLightPass(dl->numInteractionSurfaces, dl->interactionSurfaces);

		// Draw the fog plane if visible
		if (!dl->fogPlaneVisible)
			continue;

		RB_DrawFogLightPlane();
	}

	// Restore the scissor
	GL_Scissor(backEnd.scissor);

	QGL_LogPrintf("--------------------\n");
}


/*
 ==============================================================================

 VOLUMETRIC LIGHT SHAFTS RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_ClearLightShafts
 ==================
*/
static void RB_ClearLightShafts (){

	framebuffer_t	*lightShaftsBuffer = backEnd.lightShaftsBuffer;

	// Capture the current depth if needed
	if (!backEnd.currentDepthCaptured){
		backEnd.currentDepthCaptured = true;

		R_CopyFramebufferToTexture(rg.currentDepthTexture, TMU_DEPTH, backEnd.viewport.X(), backEnd.viewport.Y(), backEnd.viewport.Width(), backEnd.viewport.Height());
	}

	// Bind and set up the framebuffer
	GL_BindFramebuffer(lightShaftsBuffer);

	R_AttachColorTextureToFramebuffer(lightShaftsBuffer, lightShaftsBuffer->colorTexture, 0, 0);

	// Set up the viewport
	GL_Viewport(lightShaftsBuffer->rect);

	// Set up the scissor
	GL_Scissor(lightShaftsBuffer->rect);

	// Clear the color buffer
	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);

	qglClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	qglClear(GL_COLOR_BUFFER_BIT);

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_BlurLightShafts
 ==================
*/
static void RB_BlurLightShafts (float sScale, float tScale){

	blurParms_t	*parms;

	// Bind the program
	GL_BindProgram(rg.blurPrograms[BLUR_5X5]);

	// Set up the program uniforms
	parms = &backEnd.blurParms[BLUR_5X5];

	R_UniformFloat2(parms->coordScale, sScale, tScale);

	// Bind the texture
	GL_BindMultitexture(rg.scratchTexture, 0);

	// Draw it
	qglBegin(GL_QUADS);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 0.0f);
	qglVertex2f(-1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 0.0f);
	qglVertex2f( 1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 1.0f);
	qglVertex2f( 1.0f,  1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 1.0f);
	qglVertex2f(-1.0f,  1.0f);
	qglEnd();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_BlendLightShafts
 ==================
*/
static void RB_BlendLightShafts (){

	// Set the GL state
	if (r_showLightShafts->integerValue)
		GL_Disable(GL_BLEND);
	else {
		GL_Enable(GL_BLEND);
		GL_BlendFunc(GL_ONE, GL_ONE);
		GL_BlendEquation(GL_FUNC_ADD);
	}

	// Set up the texture
	GL_EnableTexture(rg.lightShaftsTexture->target);
	GL_BindTexture(rg.lightShaftsTexture);

	GL_LoadIdentity(GL_TEXTURE);
	GL_TexEnv(GL_REPLACE);

	// Draw it
	qglBegin(GL_QUADS);
	qglTexCoord2f(0.0f, 0.0f);
	qglVertex2f(-1.0f, -1.0f);
	qglTexCoord2f(1.0f, 0.0f);
	qglVertex2f( 1.0f, -1.0f);
	qglTexCoord2f(1.0f, 1.0f);
	qglVertex2f( 1.0f,  1.0f);
	qglTexCoord2f(0.0f, 1.0f);
	qglVertex2f(-1.0f,  1.0f);
	qglEnd();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_AccumLightShafts
 ==================
*/
void RB_AccumLightShafts (const glqRect &rect){

	framebuffer_t		*lightShaftsBuffer = backEnd.lightShaftsBuffer;
	lightShaftsParms_t	*parms;
	stage_t				*stage;
	texture_t			*texture;
	cinData_t			data;
	glqMat4				matrix;
	glqVec4				planes[8];
	glqVec3				color;
	float				xScale, yScale;
	float				x1, y1, x2, y2;
	float				s1, t1, s2, t2;
	int					width, height;
	int					shift;
	int					i;

	if (!r_lightShafts->integerValue)
		return;

	shift = 2 - r_lightShaftsQuality->integerValue;

	// If this is the first volumetric light
	if (!backEnd.lightShaftsRendered){
		backEnd.lightShaftsRendered = true;

		width = Max(backEnd.viewport.Width() >> shift, 1);
		height = Max(backEnd.viewport.Height() >> shift, 1);

		rg.pc.renderTextures++;
		rg.pc.renderTexturePixels += width * height;

		// Resize the framebuffer if needed
		R_ResizeFramebuffer(lightShaftsBuffer, width, height, 1);

		// Clear the light shafts
		RB_ClearLightShafts();
	}

	QGL_LogPrintf("---------- RB_AccumLightShafts ----------\n");

	// Transform rectangle into normalized device coordinates
	xScale = 1.0f / lightShaftsBuffer->width;
	yScale = 1.0f / lightShaftsBuffer->height;

	x1 = s1 = ((rect.x1 - backEnd.viewport.x1) >> shift) * xScale;
	y1 = t1 = ((rect.y1 - backEnd.viewport.y1) >> shift) * yScale;
	x2 = s2 = ((rect.x2 - backEnd.viewport.x1) >> shift) * xScale;
	y2 = t2 = ((rect.y2 - backEnd.viewport.y1) >> shift) * yScale;

	x1 = x1 * 2.0f - 1.0f;
	y1 = y1 * 2.0f - 1.0f;
	x2 = x2 * 2.0f - 1.0f;
	y2 = y2 * 2.0f - 1.0f;

	// Compute the inverse of the modelview-projection matrix
	matrix = backEnd.viewParms.modelviewProjectionMatrix.Inverse();

	// Set up the clip planes
	planes[0] = backEnd.light->data.frustum[0].ToVec4();
	planes[1] = backEnd.light->data.frustum[1].ToVec4();
	planes[2] = backEnd.light->data.frustum[2].ToVec4();
	planes[3] = backEnd.light->data.frustum[3].ToVec4();
	planes[4] = backEnd.light->data.frustum[4].ToVec4();
	planes[5] = backEnd.light->data.frustum[5].ToVec4();

	if (backEnd.viewParms.viewType != VIEW_MIRROR)
		planes[6].Clear();
	else
		planes[6] = backEnd.viewParms.clipPlane.ToVec4();

	planes[7][0] = backEnd.viewParms.axis[0][0];
	planes[7][1] = backEnd.viewParms.axis[0][1];
	planes[7][2] = backEnd.viewParms.axis[0][2];
	planes[7][3] = -(backEnd.viewParms.origin.Dot(backEnd.viewParms.axis[0]) + r_zNear->floatValue);

	// Transform the light for this entity
	RB_TransformLightForEntity(backEnd.light, backEnd.scene->worldEntity);

	// Bind and set up the framebuffer
	GL_BindFramebuffer(lightShaftsBuffer);

	R_AttachColorTextureToFramebuffer(lightShaftsBuffer, lightShaftsBuffer->colorTexture, 0, 0);

	// Set up the viewport
	GL_Viewport(lightShaftsBuffer->rect);

	// Set up the scissor
	GL_Scissor(lightShaftsBuffer->rect);

	// Unbind the index buffer
	GL_BindIndexBuffer(NULL);

	// Unbind the vertex buffer
	GL_BindVertexBuffer(NULL);

	// Set the GL state
	GL_LoadIdentity(GL_PROJECTION);
	GL_LoadIdentity(GL_MODELVIEW);

	GL_PolygonMode(GL_FILL);

	GL_Enable(GL_CULL_FACE);
	GL_CullFace(GL_BACK);

	GL_Disable(GL_POLYGON_OFFSET_FILL);

	GL_Enable(GL_BLEND);
	GL_BlendFunc(GL_ONE, GL_ONE);
	GL_BlendEquation(GL_FUNC_ADD);

	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_DepthRange(0.0f, 1.0f);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	// Disable the clip plane if needed
	if (backEnd.viewParms.viewType == VIEW_MIRROR)
		qglDisable(GL_CLIP_PLANE0);

	// Bind the program
	GL_BindProgram(rg.lightShaftsPrograms[backEnd.light->l.type]);

	// Run through the light stages
	for (i = 0, stage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, stage++){
		if (!backEnd.lightRegisters[stage->conditionRegister])
			continue;

		// Compute the light matrix
		RB_ComputeLightMatrix(backEnd.light, backEnd.scene->worldEntity, &stage->textureStage);

		// Compute the shadow matrix
		RB_ComputeShadowMatrix(backEnd.light, backEnd.scene->worldEntity);

		// Compute the light color
		color[0] = backEnd.lightRegisters[stage->colorStage.registers[0]] * r_lightScale->floatValue;
		color[1] = backEnd.lightRegisters[stage->colorStage.registers[1]] * r_lightScale->floatValue;
		color[2] = backEnd.lightRegisters[stage->colorStage.registers[2]] * r_lightScale->floatValue;

		// Get the light projection texture
		texture = stage->textureStage.texture;

		// If we have a cinematic
		if (stage->textureStage.cinematicHandle){
			if (r_skipVideos->integerValue)
				texture = rg.blackTexture;
			else {
				// Decode a video frame
				data = CIN_UpdateCinematic(stage->textureStage.cinematicHandle, backEnd.time);

				// Update the texture if needed
				if (!data.image)
					texture = rg.blackTexture;
				else if (data.dirty)
					R_UploadTextureImage(texture, TMU_LIGHTPROJECTION, data.image, data.width, data.height);
			}
		}

		// Set up the program uniforms
		parms = &backEnd.lightShaftsParms[backEnd.light->l.type];

		R_UniformVector3(parms->viewOrigin, backEnd.viewParms.origin);

		if (backEnd.light->l.type == RL_CUBIC){
			R_UniformVector3(parms->lightOrigin, backEnd.localParms.lightOrigin);
			R_UniformMatrix3(parms->lightAxis, GL_TRUE, backEnd.localParms.lightAxis);
		}

		R_UniformMatrix4(parms->unprojectMatrix, GL_FALSE, matrix);
		R_UniformMatrix4(parms->lightMatrix, GL_TRUE, backEnd.localParms.lightMatrix);
		R_UniformMatrix4(parms->shadowMatrix, GL_TRUE, backEnd.localParms.shadowMatrix);
		R_UniformVector3(parms->lightColor, color);

		if (backEnd.light->l.type == RL_PROJECTED)
			R_UniformVector4(parms->lightPlane, backEnd.localParms.lightPlane);

		R_UniformVector4Array(parms->clipPlanes, 8, planes);
		R_UniformFloat2(parms->depthScaleAndBias, -backEnd.shadowMap.projectionMatrix[2][2], backEnd.shadowMap.projectionMatrix[3][2]);

		if (!r_lightShaftsShadows->integerValue || !backEnd.shadowMap.available || !backEnd.light->l.volumeShadows)
			R_UniformFloat4(parms->lightShaftsParms, 0.0f, 1.0f / 32.0f, r_lightShaftsSampleStep->floatValue, backEnd.light->l.volumeIntensity);
		else
			R_UniformFloat4(parms->lightShaftsParms, 1.0f, 1.0f / 32.0f, r_lightShaftsSampleStep->floatValue, backEnd.light->l.volumeIntensity);

		// Bind the textures
		GL_BindMultitexture(texture, TMU_LIGHTPROJECTION);
		GL_BindMultitexture(backEnd.lightMaterial->lightFalloffImage, TMU_LIGHTFALLOFF);

		if (backEnd.light->l.type == RL_CUBIC)
			GL_BindMultitexture(backEnd.lightMaterial->lightCubeImage, TMU_LIGHTCUBE);

		GL_BindMultitexture(backEnd.shadowMap.shadowTexture, TMU_SHADOW);
		GL_BindMultitexture(rg.noiseTexture, TMU_JITTER);
		GL_BindMultitexture(rg.currentDepthTexture, TMU_DEPTH);

		// Draw it
		qglBegin(GL_QUADS);
		qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, s1, t1);
		qglVertex2f(x1, y1);
		qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, s2, t1);
		qglVertex2f(x2, y1);
		qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, s2, t2);
		qglVertex2f(x2, y2);
		qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, s1, t2);
		qglVertex2f(x1, y2);
		qglEnd();

		// Check for errors
		if (!r_ignoreGLErrors->integerValue)
			GL_CheckForErrors();
	}

	// Unbind the framebuffer
	GL_BindFramebuffer(NULL);

	// Restore the viewport
	GL_Viewport(backEnd.viewport);

	// Restore the scissor
	GL_Scissor(backEnd.scissor);

	// Restore the GL state
	GL_LoadMatrix(GL_PROJECTION, backEnd.viewParms.projectionMatrix);

	GL_SelectTexture(0);

	// Enable the clip plane if needed
	if (backEnd.viewParms.viewType == VIEW_MIRROR)
		qglEnable(GL_CLIP_PLANE0);

	// Unbind the program
	GL_BindProgram(NULL);

	QGL_LogPrintf("--------------------\n");
}

/*
 ==================
 RB_RenderLightShafts
 ==================
*/
void RB_RenderLightShafts (){

	framebuffer_t	*lightShaftsBuffer = backEnd.lightShaftsBuffer;

	if (!backEnd.lightShaftsRendered)
		return;

	QGL_LogPrintf("---------- RB_RenderLightShafts ----------\n");

	// Unbind the index buffer
	GL_BindIndexBuffer(NULL);

	// Unbind the vertex buffer
	GL_BindVertexBuffer(NULL);

	// Set the GL state
	GL_LoadIdentity(GL_PROJECTION);
	GL_LoadIdentity(GL_MODELVIEW);

	GL_PolygonMode(GL_FILL);

	GL_Disable(GL_CULL_FACE);
	GL_Disable(GL_POLYGON_OFFSET_FILL);
	GL_Disable(GL_BLEND);
	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_DepthRange(0.0f, 1.0f);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	// Disable the clip plane if needed
	if (backEnd.viewParms.viewType == VIEW_MIRROR)
		qglDisable(GL_CLIP_PLANE0);

	// Bind and set up the framebuffer
	GL_BindFramebuffer(lightShaftsBuffer);

	R_AttachColorTextureToFramebuffer(lightShaftsBuffer, lightShaftsBuffer->colorTexture, 0, 0);

	// Set up the viewport
	GL_Viewport(lightShaftsBuffer->rect);

	// Set up the scissor
	GL_Scissor(lightShaftsBuffer->rect);

	// Capture framebuffer
	R_CopyFramebufferToTexture(rg.scratchTexture, 0, 0, 0, lightShaftsBuffer->width, lightShaftsBuffer->height);

	// Blur horizontally
	RB_BlurLightShafts(1.0f / lightShaftsBuffer->width, 0.0f);

	// Capture framebuffer
	R_CopyFramebufferToTexture(rg.scratchTexture, 0, 0, 0, lightShaftsBuffer->width, lightShaftsBuffer->height);

	// Blur vertically
	RB_BlurLightShafts(0.0f, 1.0f / lightShaftsBuffer->height);

	// Unbind the framebuffer
	GL_BindFramebuffer(NULL);

	// Restore the viewport
	GL_Viewport(backEnd.viewport);

	// Restore the scissor
	GL_Scissor(backEnd.scissor);

	// Unbind the program
	GL_BindProgram(NULL);

	// Blend the light shafts over the screen
	RB_BlendLightShafts();

	// Restore the GL state
	GL_LoadMatrix(GL_PROJECTION, backEnd.viewParms.projectionMatrix);

	// Enable the clip plane if needed
	if (backEnd.viewParms.viewType == VIEW_MIRROR)
		qglEnable(GL_CLIP_PLANE0);

	QGL_LogPrintf("--------------------\n");
}


/*
 ==============================================================================

 BLOOM AND COLOR CORRECTION POST-PROCESSING

 ==============================================================================
*/


/*
 ==================
 RB_BloomFilter
 ==================
*/
static void RB_BloomFilter (const postProcessParms_t *postProcessParms, float sScale, float tScale){

	bloomParms_t	*parms;

	// Bind the program
	GL_BindProgram(rg.bloomProgram);

	// Set up the program uniforms
	parms = &backEnd.bloomParms;

	R_UniformFloat2(parms->stOffset1, 0.5f * sScale, 0.0f);
	R_UniformFloat2(parms->stOffset2, 0.0f, 0.5f * tScale);
	R_UniformFloat2(parms->stOffset3, 0.5f * sScale, 0.5f * tScale);
	R_UniformFloat(parms->bloomContrast, postProcessParms->bloomContrast);
	R_UniformFloat(parms->bloomThreshold, postProcessParms->bloomThreshold);

	// Bind the texture
	GL_BindMultitexture(rg.currentColorTexture, 0);

	// Draw it
	qglBegin(GL_QUADS);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 0.0f);
	qglVertex2f(-1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 0.0f);
	qglVertex2f( 1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 1.0f);
	qglVertex2f( 1.0f,  1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 1.0f);
	qglVertex2f(-1.0f,  1.0f);
	qglEnd();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_BloomBlur
 ==================
*/
static void RB_BloomBlur (const postProcessParms_t *postProcessParms, float sScale, float tScale){

	blurParms_t	*parms;

	// Bind the program
	GL_BindProgram(rg.blurPrograms[BLUR_17X17]);

	// Set up the program uniforms
	parms = &backEnd.blurParms[BLUR_17X17];

	R_UniformFloat2(parms->coordScale, sScale, tScale);

	// Bind the texture
	GL_BindMultitexture(rg.bloomTexture, 0);

	// Draw it
	qglBegin(GL_QUADS);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 0.0f);
	qglVertex2f(-1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 0.0f);
	qglVertex2f( 1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 1.0f);
	qglVertex2f( 1.0f,  1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 1.0f);
	qglVertex2f(-1.0f,  1.0f);
	qglEnd();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_ColorCorrection
 ==================
*/
static void RB_ColorCorrection (const postProcessParms_t *postProcessParms){

	colorCorrectionParms_t	*parms;

	// Bind the program
	GL_BindProgram(rg.colorCorrectionProgram);

	// Set up the program uniforms
	parms = &backEnd.colorCorrectionParms;

	R_UniformFloat(parms->baseIntensity, postProcessParms->baseIntensity);
	R_UniformFloat(parms->glowIntensity, postProcessParms->glowIntensity);
	R_UniformVector3(parms->colorShadows, postProcessParms->colorShadows);
	R_UniformVector3(parms->colorHighlights, postProcessParms->colorHighlights);
	R_UniformVector3(parms->colorMidtones, postProcessParms->colorMidtones);
	R_UniformVector3(parms->colorMinOutput, postProcessParms->colorMinOutput);
	R_UniformVector3(parms->colorMaxOutput, postProcessParms->colorMaxOutput);
	R_UniformVector3(parms->colorSaturation, postProcessParms->colorSaturation);
	R_UniformVector3(parms->colorTint, postProcessParms->colorTint);

	// Bind the textures
	if (r_showBloom->integerValue)
		GL_BindMultitexture(rg.blackTexture, 0);
	else
		GL_BindMultitexture(rg.currentColorTexture, 0);

	if (r_bloom->integerValue)
		GL_BindMultitexture(rg.bloomTexture, 1);
	else
		GL_BindMultitexture(rg.blackTexture, 1);

	GL_BindMultitexture(rg.colorTableTexture, 2);

	// Draw it
	qglBegin(GL_QUADS);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 0.0f);
	qglVertex2f(-1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 0.0f);
	qglVertex2f( 1.0f, -1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 1.0f, 1.0f);
	qglVertex2f( 1.0f,  1.0f);
	qglVertexAttrib2f(GL_ATTRIB_TEXCOORD, 0.0f, 1.0f);
	qglVertex2f(-1.0f,  1.0f);
	qglEnd();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_PostProcess
 ==================
*/
void RB_PostProcess (const postProcessParms_t *postProcessParms){

	glqRect	rect;
	float	sScale, tScale;

	if (!r_postProcess->integerValue || rg.envShotRendering)
		return;

	if (!backEnd.viewParms.primaryView || backEnd.viewParms.viewType != VIEW_MAIN)
		return;

	QGL_LogPrintf("---------- RB_PostProcess ----------\n");

	// Unbind the index buffer
	GL_BindIndexBuffer(NULL);

	// Unbind the vertex buffer
	GL_BindVertexBuffer(NULL);

	// Set the GL state
	GL_LoadIdentity(GL_PROJECTION);
	GL_LoadIdentity(GL_MODELVIEW);

	GL_PolygonMode(GL_FILL);

	GL_Disable(GL_CULL_FACE);
	GL_Disable(GL_POLYGON_OFFSET_FILL);
	GL_Disable(GL_BLEND);
	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_DepthRange(0.0f, 1.0f);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	// Capture framebuffer
	R_CopyFramebufferToTexture(rg.currentColorTexture, 0, backEnd.viewport.X(), backEnd.viewport.Y(), backEnd.viewport.Width(), backEnd.viewport.Height());

	// Perform bloom post-processing if desired
	if (r_bloom->integerValue){
		rect.Set(0, 0, Max(backEnd.viewport.Width() >> 2, 1), Max(backEnd.viewport.Height() >> 2, 1));

		sScale = 1.0f / rect.Width();
		tScale = 1.0f / rect.Height();

		// Set up the viewport
		GL_Viewport(rect);

		// Set up the scissor
		GL_Scissor(rect);

		// Downsample and filter out dark pixels
		RB_BloomFilter(postProcessParms, sScale, tScale);

		// Capture framebuffer
		R_CopyFramebufferToTexture(rg.bloomTexture, 0, rect.X(), rect.Y(), rect.Width(), rect.Height());

		// Blur horizontally
		RB_BloomBlur(postProcessParms, sScale, 0.0f);

		// Capture framebuffer
		R_CopyFramebufferToTexture(rg.bloomTexture, 0, rect.X(), rect.Y(), rect.Width(), rect.Height());

		// Blur vertically
		RB_BloomBlur(postProcessParms, 0.0f, tScale);

		// Capture framebuffer
		R_CopyFramebufferToTexture(rg.bloomTexture, 0, rect.X(), rect.Y(), rect.Width(), rect.Height());

		// Restore the viewport
		GL_Viewport(backEnd.viewport);

		// Restore the scissor
		GL_Scissor(backEnd.scissor);
	}

	// Draw the final post-processed image
	RB_ColorCorrection(postProcessParms);

	// Restore the GL state
	GL_LoadMatrix(GL_PROJECTION, backEnd.viewParms.projectionMatrix);

	GL_SelectTexture(0);

	// Unbind the program
	GL_BindProgram(NULL);

	QGL_LogPrintf("--------------------\n");
}


/*
 ==============================================================================

 2D RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_DrawMaterial2D
 ==================
*/
void RB_DrawMaterial2D (){

	stage_t	*stage;
	int		i;

	QGL_LogPrintf("----- RB_DrawMaterial2D ( %s ) -----\n", backEnd.material->name);

	for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++){
		if (stage->lighting != SL_AMBIENT)
			continue;

		if (!backEnd.registers[stage->conditionRegister])
			continue;

		// Special case for custom shaders
		if (stage->shaderStage.program){
			if (r_skipShaders->integerValue)
				continue;

			RB_DrawState(stage);

			RB_SetupShaderStage(&stage->shaderStage);

			RB_DrawElements();

			RB_CleanupShaderStage(&stage->shaderStage);

			continue;
		}

		// General case
		RB_DrawState(stage);

		RB_SetupTextureStage(&stage->textureStage);
		RB_SetupColorStage(&stage->colorStage);

		RB_DrawElements();

		RB_CleanupColorStage(&stage->colorStage);
		RB_CleanupTextureStage(&stage->textureStage);
	}

	QGL_LogPrintf("--------------------\n");
}